package ping

import (
	"errors"
	"math/rand"
	"net"
	"os"
	"time"

	"os/exec"
	"regexp"
	"runtime"
	"strconv"

	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
)

func (p *Ping) ICMP(host string, count int, debug bool) error {
	if count <= 0 {
		count = p.PingCount
	}
	var Data = []byte("Test Request Package Information")
	pi, err := createICMP(host, count, Data)
	pi.debug = debug
	if err != nil {
		return err
	}
	return pi.icmp(count)
}

func createICMP(addr string, req int, data []byte) (*icmpInfo, error) {
	wb, err := MarshalMsg(req, data)
	if err != nil {
		return nil, err
	}
	addr, err = getIp4(addr)
	if err != nil {
		return nil, err
	}
	return &icmpInfo{Data: wb, Addr: addr}, nil
}

func (p *icmpInfo) dail() (err error) {
	p.Conn, err = net.Dial("ip4:icmp", p.Addr)
	if err != nil {
		return err
	}
	return nil
}

func (p *icmpInfo) icmp(count int) error {
	if err := p.dail(); err != nil {
		return err
	}
	if p.debug {
		println("Start PingInfo from ", p.Conn.LocalAddr())
	}
	err := p.Conn.SetDeadline(time.Now().Add(10 * time.Second))
	if err != nil {
		return errors.New("截止时间设置失败")
	}
	for i := 0; i < count; i++ {
		// 发送ICMP请求
		_, err := p.Conn.Write(p.Data)
		if err != nil {
			return err
		}
		// 接收回复
		buf := make([]byte, 1500)
		_, err = p.Conn.Read(buf)
		if err != nil {
			return err
		}
		time.Sleep(1e9)
	}
	return nil
}

func MarshalMsg(req int, data []byte) ([]byte, error) {
	xid, xseq := os.Getpid()&0xffff, req
	wm := icmp.Message{
		Type: ipv4.ICMPTypeEcho, Code: 0,
		Body: &icmp.Echo{
			ID: xid, Seq: xseq,
			Data: data,
		},
	}
	return wm.Marshal(nil)
}

func getIp4(host string) (string, error) {
	adders, err := net.LookupHost(host)
	if err != nil {
		return "", err
	}
	if len(adders) < 1 {
		return "", errors.New("unknown host")
	}
	rd := rand.New(rand.NewSource(time.Now().UnixNano()))
	return adders[rd.Intn(len(adders))], nil
}

// isAdmin 检查当前进程是否为管理员权限（仅Windows有效）
func isAdmin() bool {
	if runtime.GOOS != "windows" {
		return true // 非Windows系统默认true
	}
	// Windows下判断管理员权限 - 简化版本
	_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
	return err == nil
}

// ICMPWithStats 执行ICMP ping并返回详细的统计信息
func (p *Ping) ICMPWithStats(host string, count int, debug bool) (*PingResult, error) {
	if runtime.GOOS == "windows" && !isAdmin() {
		println("检测到当前不是管理员权限，ICMP原生功能不可用，将自动调用系统ping命令。")
		return p.ICMPWithSysPing(host, count, debug)
	}
	if count <= 0 {
		count = p.PingCount
	}

	result := &PingResult{
		Host:     host,
		Sent:     count,
		Received: 0,
		RTTs:     make([]time.Duration, 0, count),
	}

	var Data = []byte("Test Request Package Information")
	pi, err := createICMP(host, count, Data)
	if err != nil {
		return result, err
	}
	pi.debug = debug

	err = pi.icmpWithStats(count, result)
	if err != nil {
		return result, err
	}

	// 计算统计信息
	result.CalculateLossRate()
	result.CalculateRTTStats()

	return result, nil
}

// icmpWithStats 执行ICMP ping并收集统计信息
func (p *icmpInfo) icmpWithStats(count int, result *PingResult) error {
	if err := p.dail(); err != nil {
		return err
	}
	if p.debug {
		println("Start PingInfo from ", p.Conn.LocalAddr())
	}
	err := p.Conn.SetDeadline(time.Now().Add(10 * time.Second))
	if err != nil {
		return errors.New("截止时间设置失败")
	}

	for i := 0; i < count; i++ {
		start := time.Now()

		// 发送ICMP请求
		_, err := p.Conn.Write(p.Data)
		if err != nil {
			if p.debug {
				println("发送失败:", err.Error())
			}
			continue
		}

		// 接收回复
		buf := make([]byte, 1500)
		_, err = p.Conn.Read(buf)
		if err != nil {
			if p.debug {
				println("接收失败:", err.Error())
			}
			continue
		}

		// 计算RTT
		rtt := time.Since(start)
		result.Received++
		result.RTTs = append(result.RTTs, rtt)

		if p.debug {
			println("Ping", i+1, "RTT:", rtt)
		}

		time.Sleep(1e9)
	}

	return nil
}

// ICMPWithSysPing 使用系统ping命令实现，适用于无管理员权限的Windows
func (p *Ping) ICMPWithSysPing(host string, count int, debug bool) (*PingResult, error) {
	if count <= 0 {
		count = p.PingCount
	}
	result := &PingResult{
		Host:     host,
		Sent:     count,
		Received: 0,
		RTTs:     make([]time.Duration, 0, count),
	}
	cmd := exec.Command("ping", host, "-n", strconv.Itoa(count))
	output, err := cmd.CombinedOutput()
	if err != nil {
		return result, err
	}
	outStr := string(output)
	if debug {
		println(outStr)
	}
	// 解析输出
	// 统计接收数、丢包数、丢包率
	// 解析RTT
	// 适配中文和英文Windows
	// 1. 匹配丢包率
	lossRe := regexp.MustCompile(`(丢失 = (\d+))|(Lost = (\d+))`)
	match := lossRe.FindStringSubmatch(outStr)
	if len(match) >= 3 && match[2] != "" {
		result.Lost, _ = strconv.Atoi(match[2])
	} else if len(match) >= 5 && match[4] != "" {
		result.Lost, _ = strconv.Atoi(match[4])
	}
	// 2. 匹配接收数
	recvRe := regexp.MustCompile(`(已接收 = (\d+))|(Received = (\d+))`)
	match = recvRe.FindStringSubmatch(outStr)
	if len(match) >= 3 && match[2] != "" {
		result.Received, _ = strconv.Atoi(match[2])
	} else if len(match) >= 5 && match[4] != "" {
		result.Received, _ = strconv.Atoi(match[4])
	}
	// 3. 匹配丢包率百分比
	lossRateRe := regexp.MustCompile(`(\d+)%[\s]*丢失|Lost = \d+ \((\d+)% loss\)`)
	match = lossRateRe.FindStringSubmatch(outStr)
	if len(match) >= 2 && match[1] != "" {
		result.LossRate, _ = strconv.ParseFloat(match[1], 64)
	} else if len(match) >= 3 && match[2] != "" {
		result.LossRate, _ = strconv.ParseFloat(match[2], 64)
	}
	// 4. 匹配RTT
	rttRe := regexp.MustCompile(`最短 = (\d+)ms, 最长 = (\d+)ms, 平均 = (\d+)ms|Minimum = (\d+)ms, Maximum = (\d+)ms, Average = (\d+)ms`)
	match = rttRe.FindStringSubmatch(outStr)
	if len(match) >= 4 && match[1] != "" && match[2] != "" && match[3] != "" {
		min, _ := strconv.Atoi(match[1])
		max, _ := strconv.Atoi(match[2])
		avg, _ := strconv.Atoi(match[3])
		result.MinRTT = time.Duration(min) * time.Millisecond
		result.MaxRTT = time.Duration(max) * time.Millisecond
		result.AvgRTT = time.Duration(avg) * time.Millisecond
	} else if len(match) >= 6 && match[4] != "" && match[5] != "" && match[6] != "" {
		min, _ := strconv.Atoi(match[4])
		max, _ := strconv.Atoi(match[5])
		avg, _ := strconv.Atoi(match[6])
		result.MinRTT = time.Duration(min) * time.Millisecond
		result.MaxRTT = time.Duration(max) * time.Millisecond
		result.AvgRTT = time.Duration(avg) * time.Millisecond
	}
	return result, nil
}
